Voxel Mosaics 1¶

Choose axial (A), coronal (C) or sagittal (S) slices. Modify with cross slices (X), renderings (R), and horizontal overlap (H).

See https://niivue.com/demos/features/mosaics.html for mirror.

In [1]:
from pathlib import Path

from ipyniivue import download_dataset

BASE_API_URL = "https://niivue.com/demos/images/"
DATA_FOLDER = Path("images")

# Download data for example
download_dataset(
    BASE_API_URL,
    DATA_FOLDER,
    files=[
        "mni152.nii.gz",
        "spmMotor.nii.gz",
    ],
)
mni152.nii.gz already exists.
Downloading spmMotor.nii.gz...
Dataset downloaded successfully to images.
In [2]:
import ipywidgets as widgets
from IPython.display import display

from ipyniivue import NiiVue

## Create NiiVue Instance and Load Data

nv = NiiVue(
    is_colorbar=True,
    is_alpha_clip_dark=True,
    center_mosaic=True,
    back_color=(1, 1, 1, 1),
)

nv.load_volumes(
    [
        {
            "path": DATA_FOLDER / "mni152.nii.gz",
            "colorbar_visible": False,
        },
        {
            "path": DATA_FOLDER / "spmMotor.nii.gz",
            "cal_min": 4.2,
            "cal_max": 8,
            "colormap": "warm",
            "colormap_negative": "winter",
        },
    ]
)

initial_mosaic = "A -20 50 60 70 C -10 -20 -50 S R X 0 R X -0"
nv.set_slice_mosaic_string(initial_mosaic)


@nv.on_canvas_attached
def setup_illumination():
    """Set volume render illumination after canvas attached."""
    nv.set_volume_render_illumination(0.3)


## Create Interactive Controls

# mosaic string input
mosaic_text = widgets.Text(
    value=initial_mosaic,
    placeholder="Enter mosaic string",
    description="Mosaic String:",
    style={"description_width": "initial"},
    layout=widgets.Layout(width="600px"),
)

# help button
help_button = widgets.Button(
    description="Help",
    button_style="info",
    tooltip="Click for information about mosaic strings",
    layout=widgets.Layout(width="80px"),
)

# outline slider
outline_slider = widgets.IntSlider(
    min=0,
    max=8,
    value=0,
    description="Outline:",
    continuous_update=True,
)

# alpha mode dropdown
alpha_dropdown = widgets.Dropdown(
    options=[
        ("Restrict colorbar to range", 0),
        ("Colorbar from 0, transparent subthreshold", 1),
        ("Colorbar from 0, translucent subthreshold", 2),
    ],
    value=0,
    description="Alpha Mode:",
    style={"description_width": "initial"},
    layout=widgets.Layout(width="400px"),
)

# glossy rendering dropdown
glossy_dropdown = widgets.Dropdown(
    options=[
        ("Slices", -1),
        ("Matte", 0),
        ("Low", 0.3),
        ("Medium", 0.6),
        ("High", 1.0),
    ],
    value=0.3,
    description="Glossy:",
    style={"description_width": "initial"},
)

# dark mode checkbox
dark_checkbox = widgets.Checkbox(
    value=False,
    description="Dark Mode",
)

# save button
save_button = widgets.Button(
    description="Save Bitmap",
    button_style="success",
    tooltip="Save current view as PNG",
)

# output for messages
output = widgets.Output()

## Setup Event Handlers


def on_mosaic_change(change):
    """Handle mosaic string changes."""
    nv.set_slice_mosaic_string(change["new"])


def on_help_click(b):
    """Display help information."""
    with output:
        output.clear_output()
        print("Mosaic String Help:")
        print("===================")
        print("Choose axial (A), coronal (C) or sagittal (S) slices.")
        print("Modify with:")
        print("  - X: cross slices")
        print("  - R: renderings")
        print("  - H: horizontal overlap (e.g., H 0.3)")
        print("")
        print("Example: 'A -20 50 60 70 C -10 -20 -50 S R X 0 R X -0'")
        print("This creates axial slices at positions -20, 50, 60, 70,")
        print("coronal slices at -10, -20, -50, a sagittal slice,")
        print("and renderings with cross sections.")


def on_outline_change(change):
    """Handle outline width changes."""
    nv.overlay_outline_width = 0.25 * change["new"]


def on_alpha_change(change):
    """Handle alpha mode changes."""
    nv.volumes[1].colormap_type = change["new"]


def on_glossy_change(change):
    """Handle glossy rendering changes."""
    value = float(change["new"])
    nv.set_volume_render_illumination(value)


def on_dark_change(change):
    """Handle dark mode toggle."""
    if change["new"]:
        nv.opts.back_color = (0, 0, 0, 1)
    else:
        nv.opts.back_color = (1, 1, 1, 1)


def on_save_click(b):
    """Save the current scene."""
    with output:
        output.clear_output()
        nv.save_scene("mosaic_screenshot.png")
        print("Scene saved as 'mosaic_screenshot.png'")


# attach event handlers
mosaic_text.observe(on_mosaic_change, names="value")
help_button.on_click(on_help_click)
outline_slider.observe(on_outline_change, names="value")
alpha_dropdown.observe(on_alpha_change, names="value")
glossy_dropdown.observe(on_glossy_change, names="value")
dark_checkbox.observe(on_dark_change, names="value")
save_button.on_click(on_save_click)

# initialize values
on_outline_change({"new": outline_slider.value})
on_alpha_change({"new": alpha_dropdown.value})

## Display All

# organize controls
mosaic_row = widgets.HBox([mosaic_text, help_button])
controls_row1 = widgets.HBox([outline_slider, alpha_dropdown])
controls_row2 = widgets.HBox([glossy_dropdown, dark_checkbox, save_button])

# create main layout
controls = widgets.VBox([mosaic_row, controls_row1, controls_row2, output])

# display everything
display(widgets.VBox([controls, nv]))